﻿using MvcMedicalProvider.DataAccess;
using MvcMedicalProvider.Helpers;
using MvcMedicalProvider.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using PagedList;

namespace MvcMedicalProvider.Controllers
{
    public class SearchProviderController : Controller
    {
        private readonly MedicalProviderDbContext _db = new MedicalProviderDbContext();
        //
        // GET: /SearchProvider/

        public ActionResult Index()
        {
            var listProductType = Session["ListProductType"] as List<ProviderType>;

            if (listProductType == null)
            {
                var providerTypes = _db.ProviderTypes.OrderBy(p => p.provider_type);
                listProductType = providerTypes.ToList();

                Session.Add("ListProductType", listProductType);
            }

            ViewBag.YourZipcodeLiteral = Constants.Constants.YourZipCode;
            ViewBag.OptionalLiteral = Constants.Constants.Optional;
            return View(listProductType);
        }

        [HttpPost]
        public ActionResult ShowResults()
        {
            string speci = Request.Form["speci"];
            var zipCode = Request.Form["zipCode"];
            var range = int.Parse(Request.Form["range"]);
            var dfname = Request.Form["dfname"];
            var dlname = Request.Form["dlname"];
            var sortBy = Request.QueryString["sortby"] ?? "1";
            var sortDirection = Request.QueryString["direction"] ?? "asc";
            var pageIndexString = Request.QueryString["page"] ?? "1";

            int pageIndex;
            if (!int.TryParse(pageIndexString, out pageIndex))
            {
                pageIndex = 1;
            }

            ViewBag.speci = speci;
            ViewBag.zipCode = zipCode;
            ViewBag.range = range;
            ViewBag.dfname = dfname;
            ViewBag.dlname = dlname;

            var listProductType = Session["ListProductType"] as List<ProviderType>;

            if (listProductType == null)
            {
                var providerTypes = _db.ProviderTypes.OrderBy(p => p.provider_type);
                listProductType = providerTypes.ToList();

                Session.Add("ListProductType", listProductType);
            }

            ViewBag.ProviderTypes = listProductType;

            var listZipCodeInRange = new List<string>();
            if (zipCode != Constants.Constants.YourZipCode)
            {
                var zipCodeForCalculation = zipCode.Substring(0, 5);
                bool hasLatLon;
                var dictZipCodeInRange = GetZipPoint(zipCodeForCalculation, range, out hasLatLon);

                if (dictZipCodeInRange.Count > 0)
                {
                    if (!dictZipCodeInRange.ContainsKey(zipCode))
                    {
                        listZipCodeInRange.Add(zipCode);
                    }
                    listZipCodeInRange.AddRange(dictZipCodeInRange.Keys);
                }
            }

            List<ProviderDetails> providers;
            List<UiDoctor> refinedDoctors;
            var canGetFromSession = CanBeGetFromSession(dlname, dfname, zipCode, speci, range);
            if (canGetFromSession)
            {
                refinedDoctors = Session["ListProviderDetails"] as List<UiDoctor>;
                if (refinedDoctors == null)
                {
                    providers = ProviderDAO.Instance.GetProviderDetails(speci, zipCode, listZipCodeInRange, dfname.ToLower(),
                                    dlname.ToLower(), sortBy, sortDirection);
                    refinedDoctors = RefineProviders(providers);
                    SetValueToSession(speci, zipCode, range, dfname, dlname, refinedDoctors);
                }
            }
            else
            {
                providers = ProviderDAO.Instance.GetProviderDetails(speci, zipCode, listZipCodeInRange, dfname.ToLower(),
                    dlname.ToLower(), sortBy, sortDirection);
                refinedDoctors = RefineProviders(providers);
                SetValueToSession(speci, zipCode, range, dfname, dlname, refinedDoctors);
            }

            refinedDoctors = SortDoctors(refinedDoctors, sortBy, sortDirection);

            ViewBag.HasResult = false;
            ViewBag.TotalItemCount = refinedDoctors.Count.ToString();
            ViewBag.PageSize = Constants.Constants.PageSize.ToString();
            ViewBag.PageIndex = pageIndex.ToString();

            if (refinedDoctors.Count > 0)
            {
                ViewBag.HasResult = true;
                ViewBag.PageRows = refinedDoctors.Count > Constants.Constants.PageSize ? Constants.Constants.PageSize : refinedDoctors.Count;
                ViewBag.OverLimit = false;
                if (refinedDoctors.Count > Constants.Constants.ResultLimitation)
                {
                    ViewBag.OverLimit = true;
                }
            }

            return View(refinedDoctors.ToPagedList(pageIndex, Constants.Constants.PageSize));
        }

        public ActionResult PreviewIndex()
        {
            var listProductType = Session["ListProductType"] as List<ProviderType>;

            if (listProductType == null)
            {
                var providerTypes = _db.ProviderTypes.OrderBy(p => p.provider_type);
                listProductType = providerTypes.ToList();

                Session.Add("ListProductType", listProductType);
            }

            ViewBag.YourZipcodeLiteral = Constants.Constants.YourZipCode;
            ViewBag.OptionalLiteral = Constants.Constants.Optional;
            return View(listProductType);
        }

        [HttpPost]
        public ActionResult PreviewResults()
        {
            string speci = Request.Form["speci"];
            var zipCode = Request.Form["zipCode"];
            var range = int.Parse(Request.Form["range"]);
            var dfname = Request.Form["dfname"];
            var dlname = Request.Form["dlname"];
            var sortBy = Request.QueryString["sortby"] ?? "1";
            var sortDirection = Request.QueryString["direction"] ?? "asc";
            var pageIndexString = Request.QueryString["page"] ?? "1";

            int pageIndex;
            if (!int.TryParse(pageIndexString, out pageIndex))
            {
                pageIndex = 1;
            }

            ViewBag.speci = speci;
            ViewBag.zipCode = zipCode;
            ViewBag.range = range;
            ViewBag.dfname = dfname;
            ViewBag.dlname = dlname;

            var listProductType = Session["ListProductType"] as List<ProviderType>;

            if (listProductType == null)
            {
                var providerTypes = _db.ProviderTypes.OrderBy(p => p.provider_type);
                listProductType = providerTypes.ToList();

                Session.Add("ListProductType", listProductType);
            }

            ViewBag.ProviderTypes = listProductType;

            var listZipCodeInRange = new List<string>();
            if (zipCode != Constants.Constants.YourZipCode)
            {
                var zipCodeForCalculation = zipCode.Substring(0, 5);
                bool hasLatLon;
                var dictZipCodeInRange = GetZipPoint(zipCodeForCalculation, range, out hasLatLon);

                if (dictZipCodeInRange.Count > 0)
                {
                    if (!dictZipCodeInRange.ContainsKey(zipCode))
                    {
                        listZipCodeInRange.Add(zipCode);
                    }
                    listZipCodeInRange.AddRange(dictZipCodeInRange.Keys);
                }
            }

            List<ProviderDetails> providers;
            List<UiDoctor> refinedDoctors;
            var canGetFromSession = CanBeGetFromSession(dlname, dfname, zipCode, speci, range);
            if (canGetFromSession)
            {
                refinedDoctors = Session["ListProviderDetails"] as List<UiDoctor>;
                if (refinedDoctors == null)
                {
                    providers = ProviderDAO.Instance.GetProviderDetails(speci, zipCode, listZipCodeInRange, dfname.ToLower(),
                                    dlname.ToLower(), sortBy, sortDirection);
                    refinedDoctors = RefineProviders(providers);
                    SetValueToSession(speci, zipCode, range, dfname, dlname, refinedDoctors);
                }
            }
            else
            {
                providers = ProviderDAO.Instance.GetProviderDetails(speci, zipCode, listZipCodeInRange, dfname.ToLower(),
                    dlname.ToLower(), sortBy, sortDirection);
                refinedDoctors = RefineProviders(providers);
                SetValueToSession(speci, zipCode, range, dfname, dlname, refinedDoctors);
            }

            refinedDoctors = SortDoctors(refinedDoctors, sortBy, sortDirection);

            ViewBag.HasResult = false;
            ViewBag.TotalItemCount = refinedDoctors.Count.ToString();
            ViewBag.PageSize = Constants.Constants.PageSize.ToString();
            ViewBag.PageIndex = pageIndex.ToString();

            if (refinedDoctors.Count > 0)
            {
                ViewBag.HasResult = true;
                ViewBag.PageRows = refinedDoctors.Count > Constants.Constants.PageSize ? Constants.Constants.PageSize : refinedDoctors.Count;
                ViewBag.OverLimit = false;
                if (refinedDoctors.Count > Constants.Constants.ResultLimitation)
                {
                    ViewBag.OverLimit = true;
                }
            }

            return View(refinedDoctors.ToPagedList(pageIndex, Constants.Constants.PageSize));
        }

        private void SetValueToSession(string speci, string zipCode, int range, string dfname, string dlname, List<UiDoctor> providers)
        {
            Session["ListProviderDetails"] = providers;
            Session["LastName"] = dlname;
            Session["FirstName"] = dfname;
            Session["ZipCode"] = zipCode;
            Session["Spec"] = speci;
            Session["Range"] = range;
        }

        private List<UiDoctor> SortDoctors(List<UiDoctor> refinedDoctors, string sortBy, string sortDirection)
        {
            if (sortBy == "1")
            {
                if (sortDirection == "asc")
                {
                    return refinedDoctors.OrderBy(x => x.DoctorLastName).ToList();
                }
                else
                {
                    return refinedDoctors.OrderByDescending(x => x.DoctorLastName).ToList();
                }
            }
            else if (sortBy == "2")
            {
                if (sortDirection == "asc")
                {
                    return refinedDoctors.OrderBy(x => x.average_submitted_chrg_amt).ToList();
                }
                else
                {
                    return refinedDoctors.OrderByDescending(x => x.average_submitted_chrg_amt).ToList();
                }
            }
            else if (sortBy == "3")
            {
                if (sortDirection == "asc")
                {
                    return refinedDoctors.OrderBy(x => x.average_Medicare_payment_amt).ToList();
                }
                else
                {
                    return refinedDoctors.OrderByDescending(x => x.average_Medicare_payment_amt).ToList();
                }
            }
            else if (sortBy == "4")
            {
                if (sortDirection == "asc")
                {
                    return refinedDoctors.OrderBy(x => x.average_submitted_chrg_amt1).ToList();
                }
                else
                {
                    return refinedDoctors.OrderByDescending(x => x.average_submitted_chrg_amt1).ToList();
                }
            }
            else
            {
                if (sortDirection == "asc")
                {
                    return refinedDoctors.OrderBy(x => x.average_Medicare_payment_amt1).ToList();
                }
                else
                {
                    return refinedDoctors.OrderByDescending(x => x.average_Medicare_payment_amt1).ToList();
                }
            }
        }

        private List<UiDoctor> RefineProviders(List<ProviderDetails> providers)
        {
            var refinedList = new List<UiDoctor>();

            for (int i = 0; i < providers.Count(); i++)
            {
                var doctor = new UiDoctor();

                var providerDetails = providers[i];
                var hcpcs_code = providerDetails.hcpcs_code;

                doctor.Gender = MvcMedicalProvider.Helpers.UIHelper.GetGenderFromDbValue(providerDetails.ProviderInfo.nppes_provider_gender);
                doctor.DoctorLastName = providerDetails.ProviderInfo.nppes_provider_last_org_name;

                var providerZip = providerDetails.ProviderInfo.nppes_provider_zip.Substring(0, 5);
                doctor.ZipCode = providerZip;
                doctor.State = providerDetails.ProviderInfo.nppes_provider_state;
                doctor.Address = providerDetails.ProviderInfo.nppes_provider_street1;

                doctor.DoctorName = MvcMedicalProvider.Helpers.UIHelper.GetProviderName(providerDetails.ProviderInfo);
                doctor.City = providerDetails.ProviderInfo.nppes_provider_city;
                var nppes_provider_first_name = providerDetails.ProviderInfo.nppes_provider_first_name;
                var nppes_provider_last_org_name = providerDetails.ProviderInfo.nppes_provider_last_org_name;

                if (hcpcs_code == "99203")
                {
                    doctor.average_submitted_chrg_amt = providerDetails.average_submitted_chrg_amt;
                    doctor.average_Medicare_payment_amt = providerDetails.average_Medicare_payment_amt;

                    var insideFlag = false;
                    for (int j = 0; j < providers.Count(); j++)
                    {
                        var hcpcs_code_sub = providers[j].hcpcs_code;
                        if (hcpcs_code_sub == "99213" && nppes_provider_first_name == providers[j].ProviderInfo.nppes_provider_first_name
                                                && nppes_provider_last_org_name == providers[j].ProviderInfo.nppes_provider_last_org_name)
                        {
                            insideFlag = true;
                            doctor.average_submitted_chrg_amt1 = providers[j].average_submitted_chrg_amt;
                            doctor.average_Medicare_payment_amt1 = providers[j].average_Medicare_payment_amt;
                        }
                    }
                    if (!insideFlag)
                    {
                        doctor.average_submitted_chrg_amt1 = -1;
                        doctor.average_Medicare_payment_amt1 = -1;
                    }
                }
                else if (hcpcs_code == "99213")
                {
                    var insideFlag = false;
                    for (int j = 0; j < providers.Count(); j++)
                    {
                        var hcpcs_code_sub = providers[j].hcpcs_code;
                        if (hcpcs_code_sub == "99203" && nppes_provider_first_name == providers[j].ProviderInfo.nppes_provider_first_name
                                                && nppes_provider_last_org_name == providers[j].ProviderInfo.nppes_provider_last_org_name)
                        {
                            insideFlag = true;
                            doctor.average_submitted_chrg_amt = providers[j].average_submitted_chrg_amt;
                            doctor.average_Medicare_payment_amt = providers[j].average_Medicare_payment_amt;
                        }
                    }
                    if (!insideFlag)
                    {
                        doctor.average_submitted_chrg_amt = -1;
                        doctor.average_Medicare_payment_amt = -1;
                    }

                    doctor.average_submitted_chrg_amt1 = providerDetails.average_submitted_chrg_amt;
                    doctor.average_Medicare_payment_amt1 = providerDetails.average_Medicare_payment_amt;
                }

                refinedList.Add(doctor);
            }

            return refinedList;
        }

        private double calculate_mileage(double lat1, double lat2, double lon1, double lon2)
        {

            lat1 = Converter.ConvertToRadians(lat1);
            lon1 = Converter.ConvertToRadians(lon1);

            lat2 = Converter.ConvertToRadians(lat2);
            lon2 = Converter.ConvertToRadians(lon2);

            var delta_lat = lat2 - lat1;
            var delta_lon = lon2 - lon1;

            var temp = Math.Pow(Math.Sin(delta_lat / 2.0), 2) + Math.Cos(lat1) * Math.Pow(Math.Sin(delta_lon / 2.0), 2);
            var distance = 3956 * 2 * Math.Atan2(Math.Sqrt(temp), Math.Sqrt(1 - temp));

            return distance;
        }

        private Dictionary<string, decimal> GetZipPoint(string zipCode, int range, out bool hasPoints)
        {
            var distanceNumber = 69.172;

            hasPoints = true;

            var results = (from z in _db.ZipCodes
                           where string.Compare(z.zip_code, zipCode, StringComparison.OrdinalIgnoreCase) == 0
                           select new ZipCodeLatLon { ZipCode = z.zip_code, Lattitude = z.lat, Longitude = z.lon }).ToList();

            if (results.Count == 0)
            {
                hasPoints = false;
                return new Dictionary<string, decimal>();
            }

            var details = results[0];

            var latRange = range / distanceNumber;
            var lonRange = Math.Abs(range / (Math.Cos(details.Lattitude) * 69.172));
            var min_lat = details.Lattitude - latRange;
            var max_lat = details.Lattitude + latRange;
            var min_lon = details.Longitude - lonRange;
            var max_lon = details.Longitude + lonRange;

            var zipCodeResults = (from z in _db.ZipCodes
                                  where z.lat >= min_lat && z.lat <= max_lat && z.lon >= min_lon && z.lon <= max_lon
                                  select new ZipCodeLatLon { ZipCode = z.zip_code, Lattitude = z.lat, Longitude = z.lon }).ToList();

            var retunredResults = new Dictionary<string, decimal>(StringComparer.OrdinalIgnoreCase);
            foreach (var zipCodeLatLon in zipCodeResults)
            {
                var distance = calculate_mileage(details.Lattitude, zipCodeLatLon.Lattitude, details.Longitude, zipCodeLatLon.Longitude);
                if (distance <= range)
                {
                    var myDecimal = Convert.ToDecimal(distance);
                    retunredResults.Add(zipCodeLatLon.ZipCode.Substring(0, 5), myDecimal);
                }
            }
            return retunredResults;
        }

        private bool CanBeGetFromSession(string lastName, string firstName, string zipCode, string spec, int range)
        {
            var sessionLastName = Session["LastName"] as string;
            var sessionFirstName = Session["FirstName"] as string;
            var sessionZipCode = Session["ZipCode"] as string;
            var sessionSpec = Session["Spec"] as string;
            var sessionRange = Session["Range"] == null ? -1 : (int)Session["Range"];

            if (string.Compare(lastName, sessionLastName, StringComparison.OrdinalIgnoreCase) == 0 &&
                string.Compare(firstName, sessionFirstName, StringComparison.OrdinalIgnoreCase) == 0 &&
                string.Compare(zipCode, sessionZipCode, StringComparison.OrdinalIgnoreCase) == 0 &&
                string.Compare(spec, sessionSpec, StringComparison.OrdinalIgnoreCase) == 0
                && sessionRange == range)
            {
                return true;
            }

            return false;
        }
    }
}
